﻿using System;
using System.ComponentModel;
using System.Linq;
using System.Security.Cryptography;
using Dycox.ComponentModel;
using Dycox.Workflows.Data;
using System.Diagnostics;
using System.Collections.Generic;

namespace Dycox.Workflows
{
    public abstract partial class Workflow : InitializableObject
    {

        public event ProcessCompletedEventHandler ProcessCompleted;
        public event StageCompletedEventHandler StageCompleted;
        //public event CompletingStageEventHandler CompletingStage;
        public event ProcessAbortedEventHandler ProcessAborted;
        public event EventHandler Registered;

        public Workflow(string name)
        {
            if (name == null)
                throw new ArgumentNullException("name");

            _name = name;
        }

        private string _name;

        public string Name
        {
            get { return _name; }
        }

        string _displayName;

        public string DisplayName
        {
            get
            {
                if (_displayName == null)
                {
                    DisplayNameAttribute dna = (DisplayNameAttribute)Attribute.GetCustomAttribute(this.GetType(), typeof(DisplayNameAttribute));
                    if (dna != null) return dna.DisplayName;
                    else return _name;
                }
                else
                    return _displayName;
            }
            set
            {
                ThrowIfNotInitializing();
                _displayName = value;
            }
        }

        string _description;

        public string Description
        {
            get
            {
                if (_description == null)
                {
                    DescriptionAttribute da = (DescriptionAttribute)Attribute.GetCustomAttribute(this.GetType(), typeof(DescriptionAttribute));
                    if (da != null) return da.Description;
                    else return null;
                }
                else
                    return _description;
            }
            set
            {
                ThrowIfNotInitializing();
                _description = value;
            }
        }

        Version _currentVersion;

        public Version CurrentVersion
        {
            get
            {
                if (_currentVersion == null)
                    _currentVersion = new Version(1, 0);
                return _currentVersion;
            }
            set
            {
                ThrowIfNotInitializing();
                _currentVersion = value;
            }
        }

        Version _compatibleVersion;

        public Version CompatibleVersion
        {
            get
            {
                if (_compatibleVersion == null)
                {
                    Version current = CurrentVersion;
                    _compatibleVersion = new Version(current.Major, 0);
                }
                return _compatibleVersion;
            }
            set
            {
                ThrowIfNotInitializing();

                if (value != null)
                {
                    Version current = CurrentVersion;
                    if (current == null)
                        throw new InvalidOperationException("Must set CurrentVersion first.");
                    if (value > current)
                        throw new InvalidOperationException("The CompatibleVersion must not higher than CurrentVersion.");
                }

                _compatibleVersion = value;
            }
        }

        private ActivationMode _processStartMode;

        public ActivationMode ProcessStartMode
        {
            get { return _processStartMode; }
            set
            {
                ThrowIfNotInitializing();
                _processStartMode = value;
            }
        }


        private StageCollection _stages;

        public StageCollection Stages
        {
            get
            {
                if (_stages == null)
                    _stages = new StageCollection(this);

                return _stages;
            }
        }

        private TransitionCollection _transtions;

        public TransitionCollection Transitions
        {
            get
            {
                if (_transtions == null)
                    _transtions = new TransitionCollection(this);

                return _transtions;
            }
        }

        private ConditionCollection _completeConditions;

        public ConditionCollection CompleteConditions
        {
            get
            {
                if (_completeConditions == null)
                    _completeConditions = new ConditionCollection();
                return _completeConditions;
            }
        }

        private Stage _entrance;

        public Stage Entrance
        {
            get
            {
                return _entrance;
            }
            internal set
            {
                _entrance = value;
            }
        }

        private Placeholder _anyStage;

        internal Placeholder AnyStage
        {
            get
            {
                if (_anyStage == null)
                {
                    _anyStage = new Placeholder("AnyStage@" + Name, this);
                }
                return _anyStage;
            }
        }

        private NoMoreStepStage _endOfThread;

        internal NoMoreStepStage NoMoreStep
        {
            get
            {
                if (_endOfThread == null)
                    _endOfThread = new NoMoreStepStage("EndOfThread@" + Name, this);
                return _endOfThread;
            }
        }

        public WFProcess CreateProcess(OperationArguments args)
        {
            if (args == null)
                throw new ArgumentNullException("args");

            EntranceExecutionContext wec = new EntranceExecutionContext(this, args);
            var result = Forward(wec);
            return result.Success ? wec.Process : null;
        }

        private void UpdateAppObject(WorkflowExecutionContext context, bool modifying)
        {
            WFProcess proc = context.Process;
            IReflectionRoot target = context.AppObject;

            if (!modifying)
            {
                target.ProcessId = proc.ProcessId;
                target.CreationTime = proc.CreationTime;
                target.StartTime = proc.StartTime;
                target.Status = proc.Status;
                target.WorkflowName = proc.WorkflowName;
                target.FinishTime = proc.FinishTime;
            }

            WFStep[] steps = proc.ActiveSteps;
            Stage[] stages;
            if (steps.Length > 0)
            {
                var coll = Stages;
                stages = steps.Select(o => coll[o.StageName]).Distinct().ToArray();
            }
            else
                stages = new Stage[0];

            target.SetCurrentStages(stages);

            //proc.ActiveStageDisplayNames = target.CurrentStages;
            //proc.AppName = target.AppName;
            //proc.AppKey = target.AppKey;
        }

        //protected virtual void OnProcessCreated(ComponentEventArgs e)
        //{
        //    if (ProcessCreated != null)
        //        ProcessCreated(this, e);
        //}

        protected virtual void OnProcessCompleted(ProcessCompletedEventArgs e)
        {
            if (ProcessCompleted != null)
                ProcessCompleted(this, e);
        }

        protected virtual void OnStageCompleted(StageCompletedEventArgs e)
        {
            if (StageCompleted != null)
                StageCompleted(this, e);
        }

        protected virtual void OnProcessAborted(ProcessAbortedEventArgs e)
        {
            if (ProcessAborted != null)
                ProcessAborted(this, e);
        }

        public bool Inject(OperationArguments args, Stage stage)
        {
            if (args == null)
                throw new ArgumentNullException("args");
            if (stage == null)
                throw new ArgumentNullException("stage");

            if (stage.Workflow != this)
                throw new ArgumentException(SR.GetString("invalid_stage_another_workflow"));

            if (stage.Usage != StageUsage.Injection)
                throw new ArgumentException(SR.GetString("invalid_stage_usage"));

            return Forward(new InjectionExecutionContext(stage, args)).Success;
        }

        public bool CompleteStage(OperationArguments args)
        {
            if (args == null)
                throw new ArgumentNullException("args");

            return Forward(new WorkflowForwardingContext(this, args)).Success;
        }

        private CompleteStageResult Forward(WorkflowForwardingContext context)
        {
            
            CompleteStageResult result;

            using (context)
            {
                context.BeginExecute();
                result = CompleteStage(context);
                context.EndExecute(result.Success && (context.DataProvider.TransactionExists || context.DataProvider.ShouldDisposeDataSource));
            }

            if (result.Success && result.HasUpcomingProcesses)
            {
                if (WorkflowManager.TransactionRequired && !context.DataProvider.TransactionExists)
                    throw new WorkflowException(SR.GetString("transaction_required"));

                foreach (OperationArguments args in result.UpcomingProcessCreationArguments)
                {
                    Workflow wf = WorkflowManager.GetWorkflow(args.WorkflowName, true);
                    args.ComponentID = context.Process.ID;
                    if (args.DataSource != context.Arguments.DataSource)
                        throw new WorkflowException(SR.GetString("invalid_datasource"));
                    
                    wf.CreateProcess(args);
                }

            }

            return result;
        }

        //protected virtual void OnCompletingStage(CompletingStageEventArgs e)
        //{
        //    if (CompletingStage != null)
        //        CompletingStage(this, e);
        //}

        //private WFFrame CreateFrame(WorkflowExecutionContext context, FrameType type)
        //{
        //    var stage = context.Stage;
        //    var process = context.Process;
        //    var step = context.Step;

        //    if (stage.Usage == StageUsage.PlaceHolder)
        //        throw new ArgumentException();

        //    if (stage.Usage == StageUsage.Entrance)
        //        step = null;

        //    WFFrame frame = process.NewFrame();
            
        //    frame.CreationTime = WorkflowManager.TimeService.Now;
        //    frame.Type = type;
        //    frame.WorkflowVersion = this.CurrentVersion.ToString();
        //    frame.Description = GetFrameDescription(process, stage, type, step, context.Arguments.AppObject);
        //    frame.WorkflowName = this.Name;
        //    frame.StageName = stage.Name;
        //    frame.StageType = stage.Usage;
        //    frame.StepId = context.Step.ID.ToString();

        //    return frame;
        //}

        //protected virtual string GetFrameDescription(WFProcess process, Stage stage, FrameType frameType, WFStep step, IReflectionRoot appObj)
        //{
        //    string id = "frame_desp_" + frameType.ToString().ToLower();

        //    return SR.GetString(id, this.DisplayName, stage.DisplayName);
        //}

        private CompleteStageResult CompleteStage(WorkflowForwardingContext context)
        {

            context.Stage.ValidateContract(context.UserState, context);

            CompleteStageResult result = new CompleteStageResult();
            var args = context.Arguments;
            var step = context.Step;
            var contract = args.UserData;
            var refobj = args.AppObject;
            var process = context.Process;
            var stage = context.Stage;

            if (FlowControl.IsComponentFinishedCore(step))
                throw new WorkflowExecutionException(SR.GetString("error_step_finished"));

            WriteStepData(context);

            stage.OnExiting(new ExitingStageEventArgs());

                //contract.Apply(args, step, this, false);

            var mfr = Control.MoveForward(context);
            
            //CompletingStageEventArgs cse = new CompletingStageEventArgs(mfr);
            //OnCompletingStage(cse);
            //if (!cse.Cancel)
            //{
                process.LastStageDisplayName = stage.DisplayName;
                process.LastActiveTime = WorkflowManager.TimeService.Now;

                if (stage == _entrance && _processStartMode == ActivationMode.Auto &&
                    process.Status != WorkflowComponentStatus.Active && process.Status != WorkflowComponentStatus.Completed)
                    Control.ActivateProcess(step);

                if (refobj != null)
                    UpdateAppObject(context, false);

                StageCompletedEventArgs sce = new StageCompletedEventArgs(mfr);
                stage.OnExited(sce);
                OnStageCompleted(sce);
                //if (sce.HasUpcomingProcesses) result.UpcomingProcessCreationArguments.AddRange(sce.UpcomingProcessCreationArguments);

                if (FlowControl.IsComponentFinishedCore(process))
                {
                    var pce = new ProcessCompletedEventArgs();
                    OnProcessCompleted(pce);
                    if (pce.HasUpcomingProcesses) result.UpcomingProcessCreationArguments.AddRange(pce.UpcomingProcessCreationArguments);
                }
                else
                {
                    foreach (Stage st in mfr.GetNewStages())
                    {
                        var esa = new EnterStageEventArgs();
                        st.OnEnter(esa);
                    }
                }

                result.Success = true;
            //}

            return result;
        }

        private void WriteStepData(WorkflowExecutionContext context)
        {
            IUser user = WorkflowManager.AuthenticationService.CurrentUser;

            Stage stage = context.Stage;
            WFStep step = context.Step;

            if (user != null)
            {
                step.UserName = user.Name;
                step.UserId = user.ID;
            }

            step.SetUserState(context.Arguments.UserData);
            step.OperatorName = context.Arguments.OperatorName ?? step.UserName ?? string.Empty;
            step.Comment = context.Arguments.Comment;

        }

        public void Fallback(OperationArguments args)
        {
            using (DataProvider provider = args.CreateProvider())
            {
                try
                {
                    provider.BeginTransaction();
                    Fallback(args, provider);
                    provider.CommitTransaction();
                }
                catch
                {
                    provider.RollbackTransaction();
                    throw;
                }
            }
        }

        private void Fallback(OperationArguments args, DataProvider provider)
        {
            FallbackContract fc = args.UserData as FallbackContract;
            if (fc == null)
                throw new WorkflowException(SR.GetString("invalid_contract"));

            WFProcess parent = null;
            FrameType frameType;
            bool procRemoved = false;

            using (WorkflowUndoContext wec = new WorkflowUndoContext(this, args, provider))
            {
                wec.BeginExecute();

                var undo = wec.UndoState;

                frameType = wec.Frame.Type;

                provider.DeepDeleteProcess(wec.Process);
                if (undo.Process.FrameCount > 0)
                {
                    provider.Insert(undo.Process);
                }
                else
                {
                    procRemoved = true;
                    if (wec.Process.ParentProcessId != null)
                    {
                        parent = provider.GetProcess(new ComponentIdentifier(wec.Process.ParentProcessId.Value));
                    }
                }
                // 注意：此时的Stage不是引发退回操作的环节，而是其前一个环节。
                //wec.Stage.GetContract(wec.Step).Undo(wec);
                wec.Restore();

                wec.EndExecute(true);

            }

            if (frameType == FrameType.Bridging)
            {
                if (parent != null)
                {
                    //! TODO: 需要增加一个标识，指示环节是否自动完成。对于自动完成的环节，需要自动退回。
                    var children = provider.GetChildProcesses(parent.ProcessId);
                    if (!children.Any(o => o.ProcessId != args.ComponentID.ProcessId))
                    {
                        Workflow wf = WorkflowManager.GetWorkflow(parent.WorkflowName, true);
                        OperationArguments a = new OperationArguments(args.UserData);
                        a.ComponentID = parent.ID;
                        wf.Fallback(a, provider);
                    }
                }
                else if (!procRemoved)
                {
                    this.Fallback(args, provider);
                }
            }
        }

        public void Modify(OperationArguments args)
        {
            using (ModifyStageContext context = new ModifyStageContext(this, args))
            {
                context.BeginExecute();

                if (context.Step.Status == WorkflowComponentStatus.Active)
                    throw new WorkflowException(SR.GetString("error_step_is_active"));

                Modify(context, context.Step, null);
#if DEBUG
                Debug.Assert(context.StackIsEmpty);
#endif
                context.EndExecute(true);
            }
        }

        private void Modify(ModifyStageContext context, WFStep step, WFStep previous)
        {
            context.PushState(step, previous);

            var status = step.Status;

            Debug.Assert(status != WorkflowComponentStatus.Idling, "Why step in idling status?");

            if (status == WorkflowComponentStatus.Completed)
            {
                Stage stage = context.Workflow.Stages[step.StageName];
                if (context.IsModificationSource)
                {
                    ValidateModify(context);
                    context.UpdateContract(context.UserState);
                }
                stage.OnModifying(new ModifyStageEventArgs(context));
            }

            if (status != WorkflowComponentStatus.Aborted)
                Control.UpdateHints(step, context);

            if (status == WorkflowComponentStatus.Completed || status == WorkflowComponentStatus.Aborted)
            {
                WFStep next = step.Thread.StepsInternal
                    .Where(o => o.Sequence > step.Sequence)
                    .OrderBy(o => o.Sequence)
                    .FirstOrDefault();

                if (next != null && next.Status != WorkflowComponentStatus.Idling)
                    Modify(context, next, step);

                foreach (var t in step.Thread.Process.ThreadsInternal)
                {
                    if (t.ParentThreadStep == step.Sequence && t.ParentThreadId == step.ThreadId)
                    {
                        next = t.FirstStep;
                        if (next.Status != WorkflowComponentStatus.Idling)
                            Modify(context, next, step);
                    }
                }
            }

            context.PopState();
        }

        private void ValidateModify(ModifyStageContext context)
        {
            // 由于流程的走向的控制并不仅仅依赖于当前所使用的StageOperationContract，
            // 可能依赖于AppObject，也可能依赖于DWC的组件，还有可能这种依赖关系并不仅仅局限于当前环节的操作契约。
            // 因此，不能通过简单的对当前修改的契约内容来判断是否有效。
            // 鉴于这个原因，DWC暂时不对修改时的内容进行有效性验证，如有必要，可以在Stage.Modified事件中进行处理。
            // effun 2013-6-17

            //StageOperationContract c0, c1;
            //Type type;

            //c0 = context.Step.GetUserState(context.Stage);
            //c1 = context.UserState;
            //type = c1.GetType();

            //if (c0.GetType() != type || type != context.Stage.ContractType)
            //    throw new WorkflowException(SR.GetString("contract_type_not_match"));

            //var array = context.Stage.GetDependentProperties();

            //foreach (var p in array)
            //{
            //    if (p.ComponentType == type)
            //    {
            //        object v0, v1;

            //        v0 = p.GetValue(c0);
            //        v1 = p.GetValue(c1);

            //        if (!object.Equals(v0, v1))
            //            throw new WorkflowException(SR.GetString("dependent_property_changed", p.Name, type.FullName));
            //    }
            //}
        }

        public void AbortProcess(OperationArguments args)
        {
            using (var context = new AbortProcessContext(this, args))
            {
                context.BeginExecute();
                AbortProcess(context, true);
                context.EndExecute(context.DataProvider.ShouldDisposeDataSource);
            }
        }

        internal void AbortProcess(AbortProcessContext context, bool updateAppObject)
        {
            if (context.Step == null)
                Control.AbortProcess(context.Process, context.Reason);
            else
                Control.AbortProcess(context, context.Reason);

            if (updateAppObject && context.AppObject != null)
            {
                UpdateAppObject(context, false);
            }

            OnProcessAborted(new ProcessAbortedEventArgs(context));
        }

        public WFStep[] ActivateStep(OperationArguments args)
        {
            using (StepMaintainingContext context = new StepMaintainingContext(this, args))
            {
                IWorkflowComponent comp = context.DataProvider.GetComponent(args.ComponentID, true);

                List<WFStep> list = new List<WFStep>(10);

                if (comp is WFStep)
                {
                    if (comp.Status != WorkflowComponentStatus.Idling)
                    {
                        WFStep step0 = (WFStep)comp;
                        foreach (var step in ((WFProcess)comp).ThreadsInternal.SelectMany(o => o.StepsInternal))
                        {
                            if (step0 != step && step.Status == WorkflowComponentStatus.Idling)
                            {
                                if (Control.IsStepFollowedBy(step0, step))
                                    list.Add(step);
                            }
                        }
                    }
                    else
                        list.Add((WFStep)comp);
                }
                else if (comp is WFThread)
                {
                    list.AddRange(((WFThread)comp).StepsInternal.Where(o => o.Status == WorkflowComponentStatus.Idling));
                }
                else if (comp is WFProcess)
                {
                    list.AddRange(((WFProcess)comp).ThreadsInternal.SelectMany(o => o.StepsInternal).Where(o => o.Status == WorkflowComponentStatus.Idling));
                }

                if (list.Count > 0)
                {
                    context.BeginExecute();

                    foreach (var step in list)
                    {
                        context.SetStep(step);
                        Control.ActivateStep(context);
                    }
                    
                    context.EndExecute(context.DataProvider.ShouldDisposeDataSource);
                }

                return list.ToArray();
            }
        }

        //protected virtual void OnStageCompleted(StageDataEventArgs e)
        //{
        //    if (this.StageCompleted != null)
        //        this.StageCompleted(this, e);
        //}

        private FlowControl _control;

        internal FlowControl Control
        {
            get
            {
                if (_control == null)
                    throw new WorkflowException(SR.GetString("workflow_not_registered"));
                return _control;
            }
        }

        private ThreadLockCollection _threadLocks;

        public ThreadLockCollection ThreadLocks
        {
            get
            {
                if (_threadLocks == null)
                    _threadLocks = new ThreadLockCollection(this);
                return _threadLocks;
            }
        }

        internal protected virtual bool TestCustomCondition(ConditionTestArguments args, Conditions.CustomCondition condition)
        {
            return false;
        }


        protected override void BeginInit()
        {
        }

        protected override void EndInit()
        {
            ValidateWorkflow();
        }

        protected void ValidateWorkflow()
        {
            if (this.Entrance == null)
                throw new WorkflowException("Workflow " + this.Name + " has no entrance.");
        }

        public event PopulateHintEventHandler PopulateHint;

        protected internal virtual void OnPopulateHint(PopulateHintEventArgs e)
        {
            if (PopulateHint != null)
                PopulateHint(this, e);
        }

        internal Stage[] GetDependentStages(Stage stage)
        {
            if (stage.InjectionMode != InjectionMode.Thread)
                throw new WorkflowException("applies_on_thread_injection");

            List<Stage> list = new List<Stage>();

            foreach (var t in Transitions)
            {
                if (t.TargetStage == stage && t.Type == TransitionTypes.Normal && !list.Contains(t.SourceStage))
                {
                    list.Add(t.SourceStage);
                }
            }

            if (list.Count == 0)
                throw new WorkflowException(SR.GetString("injecting_dependency_not_found"));

            return list.ToArray();
        }

        internal Stage GetDependentStage(Stage stage)
        {
            var array = GetDependentStages(stage);
            if (array.Length > 1)
                throw new NotImplementedException("We are not ready to deal with multiple dependent stages for thread injection stage.");
            return array[0];
        }

        public bool Resume(OperationArguments args)
        {

            var id = args.ComponentID;

            using (var dp = args.CreateProvider())
            {
                WFThread thread = null;
                WFProcess proc = null;
                IWorkflowComponent comp;

                comp = dp.GetComponent(id, true);

                if (comp is WFStep)
                    thread = ((WFStep)comp).Thread;
                else if (comp is WFThread)
                    thread = (WFThread)comp;
                else
                    proc = (WFProcess)comp;


                if (thread == null && proc == null)
                    throw new WorkflowException(SR.GetString("invalid_component_id"));

                if (Control.IsComponentSuspended(comp))
                {
                    if (proc != null)
                        Control.ResumeProcess(proc, ProcessSuspendingReasons.Manual);
                    else
                        Control.ResumeThread(thread);

                    if (dp.ShouldDisposeDataSource)
                        dp.SubmitChanges();

                    return true;
                }
                else
                    return false;
            }
        }

        public bool Suspend(OperationArguments args)
        {
            using (var dp = args.CreateProvider())
            {
                WFThread thread = null;
                WFProcess proc = null;
                IWorkflowComponent comp;

                comp = dp.GetComponent(args.ComponentID, true);

                if (comp is WFStep)
                    thread = ((WFStep)comp).Thread;
                else if (comp is WFThread)
                    thread = (WFThread)comp;
                else
                    proc = (WFProcess)comp;

                if (thread == null && proc == null)
                    throw new WorkflowException(SR.GetString("invalid_component_id"));

                if (!Control.IsComponentSuspended(comp))
                {
                    if (proc != null)
                        Control.SuspendProcess(proc, ProcessSuspendingReasons.Manual);
                    else
                        Control.SuspendThread(thread);

                    if (dp.ShouldDisposeDataSource)
                        dp.SubmitChanges();

                    return true;
                }
                else
                    return false;
            }
        }

        public bool IsRegistered
        {
            get
            {
                return _control != null;
            }
        }

        internal void OnRegisteredInternal()
        {
            if (!IsRegistered)
            {
                _control = WorkflowManager.CreateControl(this);

                OnRegistered(EventArgs.Empty);
                foreach (Stage stage in Stages)
                {
                    stage.OnRegistered(EventArgs.Empty);
                }
            }
        }

        protected virtual void OnRegistered(EventArgs e)
        {
            if (Registered != null)
                Registered(this, e);
        }
    }
}
